package edu.fourtwelve.server;

import edu.fourtwelve.Data.DatabaseSingleton;
import edu.fourtwelve.srlzd_data.AccountData;
import edu.fourtwelve.srlzd_data.MemberData;
import edu.fourtwelve.srlzd_data.PasswordVerificationData;
import edu.fourtwelve.srlzd_data.Transaction;
import edu.fourtwelve.thread_execution.ThreadPool;
import edu.fourtwelve.util.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.*;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.*;

/**
 *
 * @author Christopher C. Hunt
 */
public class Server implements BankingConstants {

    private static final Logger log = Logger.getLogger(Server.class.getName());
    private static int count = 0;
    private DatabaseSingleton db;
    private ThreadPool threadPool;
    private AsymetricKeyGen asymKeyGen;

    public static void main(String[] args) {
        LoggingSetup.setupLogging(true);
        final Server server = new Server();
        server.threadPool.start();
        /*
         * Individual Connection
         */
        ServerSocket ss = null;

        try {
            ss = new ServerSocket(PORT, 10);
        } catch (IOException ex) {
            log.log(Level.SEVERE, "Failed to create ServerSocket", ex);
        }



        while (true) {
            Socket tmp = null;
            try {
                tmp = ss.accept();
            } catch (IOException ex) {
                log.log(Level.SEVERE, "Failed to accept on socket", ex);
            }

            final Socket s = tmp;


            Runnable singleSession = new Runnable() {

                @Override
                public void run() {

                    server.singleSession(s);

                }
            };

            server.threadPool.enqueueRunnable(singleSession);
        }
    }

    public Server() {
        db = DatabaseSingleton.getInstance();
        threadPool = ThreadPool.getInstance();
        try {
            asymKeyGen = new AsymetricKeyGen();
        } catch (BankingException ex) {
            log.log(Level.SEVERE, null, ex);
        }
    }

    public void singleSession(Socket s) {
        try {
            db.reopenConnection();
        } catch (SQLException ex) {
            log.log(Level.SEVERE, "Failed to reopen Connection", ex);
        }
        try {
            log.log(Level.INFO, "Serviced Connection {0}", (++count));
            final ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
            final ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
            Session session = null;
            /*
             * Individual Session
             */
            while (true) {
                
                switch (getRequestType(ois)) {
                    case AUTHENTICATE:
                        session = authenticate(ois, oos);
                        break;
                    case CREATE:
                        session = create(ois, oos);
                        break;
                    case GET_ACCOUNT_DATA:
                        getAccountData(ois, oos, session);
                        break;
                    case TRANSFER:
                        transfer(ois, oos, session);
                        break;
                    case WITHDRAWL:
                        withdrawl(ois, oos, session);
                        break;
                    case DEPOSIT:
                        deposit(ois, oos, session);
                        break;
                    default:
                        throw new BankingException("Invalid Request Type");
                }

            }
        } catch (IOException e) {
            log.log(Level.SEVERE, "{0} {1}", new Object[]{e.getClass(), e.getMessage()});
        } catch (BankingException e) {
            log.log(Level.SEVERE, "{0} {1}", new Object[]{e.getClass(), e.getMessage()});
        } finally {
            try {
                s.close();
                db.close();
            } catch (IOException ex) {
            } catch (SQLException ex) {
            }
        }
    }

    public static RequestType getRequestType(ObjectInputStream ois) throws BankingException {
        RequestType requestType = null;

        try {
            requestType = (RequestType) ois.readObject();
        } catch (IOException e) {
            String msg = "Failed to get RequestType";
            throw new BankingException(msg, e);
        } catch (ClassNotFoundException e) {
            String msg = "Failed to get RequestType";
            throw new BankingException(msg, e);
        }
        return requestType;
    }

    public Session create(final ObjectInputStream ois, final ObjectOutputStream oos) throws BankingException {
        MemberData usrData = null;
        SymetricKeyGen symKeyGen = null;
        PublicKey sessionKey = null;
        try {
            symKeyGen = new SymetricKeyGen();

            oos.writeObject((Object) asymKeyGen.getPublicKey());
            oos.flush();

            sessionKey = (PublicKey) ois.readObject();

            Cipher cipher = Cipher.getInstance(ASYMETRIC_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, sessionKey);

            SealedObject secretKey = new SealedObject(symKeyGen.getSecretKey(), cipher);
            oos.writeObject((Object) secretKey);
            oos.flush();

            SealedObject secretIV = new SealedObject(symKeyGen.getIV(), cipher);
            oos.writeObject((Object) secretIV);
            oos.flush();

            log.log(Level.INFO, "MyPublicKey = {0} YourPublicKey= {1} SecretKey= {2} IV= {3}",
                    new Object[]{Utils.toHexString(asymKeyGen.getPublicKey().getEncoded()),
                        Utils.toHexString(sessionKey.getEncoded()),
                        Utils.toHexString(symKeyGen.getSecretKey().getEncoded()),
                        Utils.toHexString(symKeyGen.getIV())});

            oos.writeObject((Object) RequestType.USER_NAME);
            oos.flush();

            Cipher decipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            decipher.init(Cipher.DECRYPT_MODE, symKeyGen.getSecretKey(), symKeyGen.getIVParams());

            SealedObject sealedUsrData = (SealedObject) ois.readObject();
            usrData = (MemberData) sealedUsrData.getObject(decipher);

            if (usrData != null && usrData.validateForInsert()) {
                try {
                    PreparedStatement pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER);
                    pstmt.setString(1, usrData.getUsrName());

                    ResultSet rs = pstmt.executeQuery();
                    if (rs.next()) {
                        oos.writeBoolean(false);
                        oos.flush();
                    } else {
                        boolean updating = false;
                        long userID = -1L;
                        pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_EXISTING_USER);
                        pstmt.setString(1, usrData.getFName());
                        pstmt.setString(2, usrData.getLName());
                        rs = pstmt.executeQuery();
                        if (rs.next()) {
                            updating = true;
                            userID = (long) rs.getInt(1);
                        }

                        long primaryAccountNum = -1L;
                        long accountId = -1L;
                        if (updating) {
                            pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER_ACCOUNTS);
                            pstmt.setInt(1, (int) userID);
                            rs = pstmt.executeQuery();

                            while (rs.next()) {
                                if ((rs.getByte(2) & DatabaseSingleton.PRIMARY_ACCOUNT_MASK)
                                        == DatabaseSingleton.PRIMARY_ACCOUNT_MASK) {
                                    primaryAccountNum = (long) rs.getInt(5);
                                }
                            }
                            if (primaryAccountNum == -1L) {
                                updating = false;
                            }
                        }

                        if (!updating) {
                            pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_ACCOUNT);
                            pstmt.setInt(1, Integer.parseInt(usrData.getPrimaryAccount()));
                            rs = pstmt.executeQuery();
                            while (rs.next()) {
                                if (((rs.getByte(2) & DatabaseSingleton.PRIMARY_ACCOUNT_MASK)
                                        == DatabaseSingleton.PRIMARY_ACCOUNT_MASK)) {
                                    accountId = (long) rs.getInt(1);
                                    primaryAccountNum = (long) rs.getInt(3);
                                }
                            }
                        }

                        try {
                            if (primaryAccountNum == Integer.parseInt(usrData.getPrimaryAccount())) {
                                oos.writeBoolean(true);
                                oos.flush();
                                if (updating) {
                                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.UPDATE_USER);
                                } else {
                                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.INSERT_USER);
                                }
                                pstmt.setString(1, usrData.getUsrName());
                                pstmt.setString(2, usrData.getFName());
                                pstmt.setString(3, usrData.getLName());
                                pstmt.setString(4, usrData.getAddress());
                                pstmt.setString(5, usrData.getAddress2());
                                pstmt.setString(6, usrData.getCity());
                                pstmt.setString(7, usrData.getState());
                                if (updating) {
                                    pstmt.setInt(8, (int) userID);
                                }

                                int rcnt = pstmt.executeUpdate();
                                log.log(Level.INFO, "INSERT RESULT = {0}", new Object[]{rcnt});

                                if (!updating) {
                                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER);
                                    pstmt.setString(1, usrData.getUsrName());

                                    rs = pstmt.executeQuery();
                                    if (rs.next()) {
                                        userID = (long) rs.getInt(1);
                                    }

                                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.INSERT_MAP_USER_ACCT);
                                    pstmt.setInt(1, (int) userID);
                                    pstmt.setInt(2, (int) accountId);

                                    rcnt = pstmt.executeUpdate();
                                    log.log(Level.INFO, "INSERT RESULT = {0}", new Object[]{rcnt});
                                }
                                pstmt = db.getConnection().prepareStatement(DatabaseSingleton.INSERT_USER_PASS);
                                pstmt.setInt(1, (int) userID);
                                pstmt.setString(2, Utils.toHexString(usrData.getPassword()));
                                pstmt.setString(3, Utils.toHexString(usrData.getSalt()));

                                rcnt = pstmt.executeUpdate();
                                log.log(Level.INFO, "INSERT RESULT = {0}", new Object[]{rcnt});
                            } else {
                                oos.writeBoolean(false);
                                oos.flush();
                            }
                        } catch (NumberFormatException e) {
                            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
                            throw new BankingException(msg, e);
                        }
                    }
                    rs.close();

                } catch (SQLException ex) {
                    log.log(Level.SEVERE, "Failure in prepared statement ", ex);
                }
            } else {
                oos.writeBoolean(false);
                oos.flush();
            }
        } catch (IOException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (ClassNotFoundException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (IllegalBlockSizeException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (InvalidKeyException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (NoSuchAlgorithmException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (NoSuchPaddingException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (InvalidAlgorithmParameterException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (BadPaddingException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        }

        return new Session(count, (usrData.getUsrName() == null) ? "" : usrData.getUsrName(), 
                (symKeyGen == null) ? null : symKeyGen.getSecretKey(), 
                (symKeyGen == null) ? null : symKeyGen.getIVParams(), sessionKey);
    }

    public Session authenticate(ObjectInputStream ois, ObjectOutputStream oos) throws BankingException {
        boolean authenticated;
        final int KEY_SIZE = 24;
        byte[] nonce = new byte[8];
        SecureRandom sr;
        try {
            sr = SecureRandom.getInstance("SHA1PRNG");
            byte[] seed = sr.generateSeed(KEY_SIZE * 4);

            int[] msSeed = new int[KEY_SIZE];
            for (int i = 0; i < KEY_SIZE * 4; i += 4) {
                msSeed[i % 4] = Utils.byteArrayToInt(new byte[]{seed[i], seed[i + 1],
                            seed[i + 2], seed[i + 3]});
            }
            MersenneTwister msRand = new MersenneTwister(msSeed);

            msRand.nextBytes(seed);
            sr.setSeed(seed);
            sr.nextBytes(nonce);

            log.log(Level.INFO, "nonce= {0}", new Object[]{Utils.toHexString(nonce)});
        } catch (NoSuchAlgorithmException ex) {
            log.log(Level.SEVERE, null, ex);
        }

        PasswordVerificationData usrData = null;
        SymetricKeyGen symKeyGen = null;
        PublicKey sessionKey = null;
        long userId = -1L;
        byte[] usrHashedPass = null;
        byte[] passForCompare;
        try {
            usrData = (PasswordVerificationData) ois.readObject();
            usrData.setNonce(nonce);
            try {
                PreparedStatement pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER);
                pstmt.setString(1, usrData.getUsrName());

                ResultSet rs = pstmt.executeQuery();
                if (rs.next()) {
                    userId = (long) rs.getInt(1);
                }

                pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_CREDENTIALS);
                pstmt.setInt(1, (int) userId);

                rs = pstmt.executeQuery();
                if (rs.next()) {
                    usrData.setSalt(Utils.toByteArray(rs.getString(2)));
                    usrHashedPass = Utils.toByteArray(rs.getString(1));
                }
            } catch (SQLException ex) {
                log.log(Level.SEVERE, "Failure in prepared statement ", ex);
            }
            oos.writeObject((Object) usrData);
            oos.flush();
            symKeyGen = new SymetricKeyGen();

            oos.writeObject((Object) asymKeyGen.getPublicKey());
            oos.flush();

            sessionKey = (PublicKey) ois.readObject();

            Cipher cipher = Cipher.getInstance(ASYMETRIC_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, sessionKey);

            SealedObject secretKey = new SealedObject(symKeyGen.getSecretKey(), cipher);
            oos.writeObject((Object) secretKey);
            oos.flush();

            SealedObject secretIV = new SealedObject(symKeyGen.getIV(), cipher);
            oos.writeObject((Object) secretIV);
            oos.flush();

            log.log(Level.INFO, "MyPublicKey = {0} YourPublicKey= {1} SecretKey= {2} IV= {3}",
                    new Object[]{Utils.toHexString(asymKeyGen.getPublicKey().getEncoded()),
                        Utils.toHexString(sessionKey.getEncoded()),
                        Utils.toHexString(symKeyGen.getSecretKey().getEncoded()),
                        Utils.toHexString(symKeyGen.getIV())});

            usrData.setPassword(usrHashedPass);
            passForCompare = hashPass(usrData);

            oos.writeObject((Object) RequestType.USER_NAME);
            oos.flush();

            Cipher decipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            decipher.init(Cipher.DECRYPT_MODE, symKeyGen.getSecretKey(), symKeyGen.getIVParams());

            SealedObject sealedUsrData = (SealedObject) ois.readObject();
            usrData = (PasswordVerificationData) sealedUsrData.getObject(decipher);

            if (Arrays.equals(passForCompare, usrData.getPassword())) {
                authenticated = true;
                oos.writeBoolean(true);
                oos.flush();
            } else {
                authenticated = false;
                oos.writeBoolean(false);
                oos.flush();
            }

        } catch (IOException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (ClassNotFoundException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (IllegalBlockSizeException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (InvalidKeyException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (NoSuchAlgorithmException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (NoSuchPaddingException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (InvalidAlgorithmParameterException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        } catch (BadPaddingException e) {
            String msg = "Failed to create User " + e.getClass().getName() + " " + e.getMessage();
            throw new BankingException(msg, e);
        }

        Session session = new Session(count, (usrData.getUsrName() == null) ? "" : usrData.getUsrName(), 
                (symKeyGen == null) ? null : symKeyGen.getSecretKey(), 
                (symKeyGen == null) ? null : symKeyGen.getIVParams(), sessionKey);
        session.setAuthenticated(authenticated);

        return session;
    }

    public void getAccountData(ObjectInputStream ois, ObjectOutputStream oos, Session session)
            throws BankingException {
        try {
            if (session.isAuthenticated()) {
                long userId = -1L;
                //accounts data must be a List with a serializable List implementation (i.e. ArrayList)
                List<AccountData> accountsData = new ArrayList<AccountData>();

                PreparedStatement pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER);
                pstmt.setString(1, session.getUserName());

                ResultSet rs = pstmt.executeQuery();
                if (rs.next()) {
                    userId = (long) rs.getInt(1);
                }

                pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER_ACCOUNTS);
                pstmt.setInt(1, (int) userId);
                rs = pstmt.executeQuery();

                while (rs.next()) {
                    accountsData.add(new AccountData((long) rs.getInt(1), (long) rs.getInt(5), rs.getString(6),
                            new ArrayList<Transaction>(), rs.getDouble(7)));
                }

                for (AccountData cur : accountsData) {
                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_ACCOUNT_TRANSACTION);
                    pstmt.setInt(1, (int) cur.getAccountId());
                    rs = pstmt.executeQuery();

                    while (rs.next()) {
                        cur.addTransaction(new Transaction((long) rs.getInt(1), rs.getInt(2),
                                rs.getString(5), rs.getString(3), rs.getDouble(4), -1L));
                    }
                }
                SymetricKeyGen symKey =
                        new SymetricKeyGen(session.getSecretKey(), session.getIvParameterSpec());

                Cipher cipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());

                Cipher decipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
                decipher.init(Cipher.DECRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());

                //accounts data is a List with a serializable List implementation (i.e. ArrayList)
                SealedObject sealedAcctList = new SealedObject((Serializable) accountsData, cipher);

                oos.writeObject((Object) sealedAcctList);
                oos.flush();

            }
        } catch (NullPointerException e) {
            throw new BankingException("Session was not initialized ", e);
        } catch (IOException e) {
            throw new BankingException("IOException ", e);
        } catch (SQLException e) {
            throw new BankingException("Failure in SQL Statement ", e);
        } catch (InvalidKeyException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchAlgorithmException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchPaddingException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (IllegalBlockSizeException e) {
            throw new BankingException("Key Creation Failure ", e);
        }
    }

    public void transfer(ObjectInputStream ois, ObjectOutputStream oos, Session session)
            throws BankingException {
        try {
            SymetricKeyGen symKey =
                    new SymetricKeyGen(session.getSecretKey(), session.getIvParameterSpec());

            Cipher cipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());

            Cipher decipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            decipher.init(Cipher.DECRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());

            boolean success = false;
            try {
                if (session.isAuthenticated()) {
                    long userId = -1L;

                    SealedObject sealedDeposit = (SealedObject) ois.readObject();
                    Transaction deposit = (Transaction) sealedDeposit.getObject(decipher);

                    SealedObject sealedWithdrawl = (SealedObject) ois.readObject();
                    Transaction withdrawl = (Transaction) sealedWithdrawl.getObject(decipher);

                    PreparedStatement pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER);
                    pstmt.setString(1, session.getUserName());

                    ResultSet rs = pstmt.executeQuery();
                    if (rs.next()) {
                        userId = (long) rs.getInt(1);
                    }

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER_ACCOUNTS);
                    pstmt.setInt(1, (int) userId);
                    rs = pstmt.executeQuery();
                    
                    long withdrawlAcctId = -1L;
                    long depositAcctId = -1L;
                    double withdrawlAccountVal = -1.0;
                    double depositAccountVal = -1.0;
                    while (rs.next()) {
                        int accountNum = rs.getInt(5);
                        int acctId = rs.getInt(1);
                        double acctVal = rs.getDouble(7);
                        if ((long) accountNum == withdrawl.getAccountNum()) {
                            withdrawlAcctId = (long) acctId;
                            withdrawlAccountVal = acctVal;
                        }
                        if ((long) accountNum == deposit.getAccountNum()) {
                            depositAcctId = (long) acctId;
                            depositAccountVal = acctVal;
                        }
                        if (withdrawlAcctId != -1L && depositAcctId != -1L) {
                            break;
                        }
                    }

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.INSERT_TRANSACTION);
                    pstmt.setInt(1, (int) deposit.getTransactionTypeId());
                    pstmt.setString(2, deposit.getTransactionDesc());
                    pstmt.setDouble(3, deposit.getTransactionValue());
                    pstmt.setInt(4, (int) depositAcctId);

                    int result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.UPDATE_ACCOUNT);
                    double newValue = ((deposit.getTransactionTypeId() % 2) == 1)
                            ? (depositAccountVal + deposit.getTransactionValue())
                            : (depositAccountVal - deposit.getTransactionValue());
                    pstmt.setDouble(1, newValue);
                    pstmt.setInt(2, (int) depositAcctId);

                    result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.INSERT_TRANSACTION);
                    pstmt.setInt(1, (int) withdrawl.getTransactionTypeId());
                    pstmt.setString(2, withdrawl.getTransactionDesc());
                    pstmt.setDouble(3, withdrawl.getTransactionValue());
                    pstmt.setInt(4, (int) withdrawlAcctId);

                    result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.UPDATE_ACCOUNT);
                    newValue = ((withdrawl.getTransactionTypeId() % 2) == 1)
                            ? (withdrawlAccountVal + withdrawl.getTransactionValue())
                            : (withdrawlAccountVal - withdrawl.getTransactionValue());
                    pstmt.setDouble(1, newValue);
                    pstmt.setInt(2, (int) withdrawlAcctId);

                    result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    success = true;
                }
            } catch (NullPointerException e) {
                throw new BankingException("Session was not initialized ", e);
            } catch (IOException e) {
                throw new BankingException("IOException ", e);
            } catch (SQLException e) {
                throw new BankingException("Failure in SQL Statement ", e);
            } catch (IllegalBlockSizeException e) {
                throw new BankingException("Key Creation Failure ", e);
            } catch (ClassNotFoundException e) {
                throw new BankingException("Casting Failure ", e);
            } catch (BadPaddingException e) {
                throw new BankingException("Key Creation Failure ", e);
            } finally {
                try {
                    SealedObject sealedBool = new SealedObject(success, cipher);
                    oos.writeObject((Object) sealedBool);
                    oos.flush();
                } catch (IOException e) {
                    throw new BankingException("IOException ", e);
                } catch (IllegalBlockSizeException e) {
                    throw new BankingException("Key Creation Failure ", e);
                }
            }
        } catch (InvalidKeyException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchAlgorithmException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchPaddingException e) {
            throw new BankingException("Key Creation Failure ", e);
        }
    }

    public void deposit(ObjectInputStream ois, ObjectOutputStream oos, Session session)
            throws BankingException {
        try {
            SymetricKeyGen symKey =
                    new SymetricKeyGen(session.getSecretKey(), session.getIvParameterSpec());

            Cipher cipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());

            Cipher decipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            decipher.init(Cipher.DECRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());
            boolean success = false;
            try {
                if (session.isAuthenticated()) {
                    long userId = -1L;

                    SealedObject sealedDeposit = (SealedObject) ois.readObject();
                    Transaction deposit = (Transaction) sealedDeposit.getObject(decipher);

                    PreparedStatement pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER);
                    pstmt.setString(1, session.getUserName());

                    ResultSet rs = pstmt.executeQuery();
                    if (rs.next()) {
                        userId = (long) rs.getInt(1);
                    }

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER_ACCOUNTS);
                    pstmt.setInt(1, (int) userId);
                    rs = pstmt.executeQuery();
                    long depositAcctId = -1L;
                    double depositAccountVal = -1.0;
                    while (rs.next()) {
                        if ((long) rs.getInt(5) == deposit.getAccountNum()) {
                            depositAcctId = (long) rs.getInt(1);
                            depositAccountVal = rs.getDouble(7);
                            break;
                        }
                    }

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.INSERT_TRANSACTION);
                    pstmt.setInt(1, (int) deposit.getTransactionTypeId());
                    pstmt.setString(2, deposit.getTransactionDesc());
                    pstmt.setDouble(3, deposit.getTransactionValue());
                    pstmt.setInt(4, (int) depositAcctId);

                    int result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.UPDATE_ACCOUNT);
                    double newValue = ((deposit.getTransactionTypeId() % 2) == 1)
                            ? (depositAccountVal + deposit.getTransactionValue())
                            : (depositAccountVal - deposit.getTransactionValue());
                    pstmt.setDouble(1, newValue);
                    pstmt.setInt(2, (int) depositAcctId);

                    result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    success = true;
                }
            } catch (NullPointerException e) {
                throw new BankingException("Session was not initialized ", e);
            } catch (IOException e) {
                throw new BankingException("IOException ", e);
            } catch (SQLException e) {
                throw new BankingException("Failure in SQL Statement ", e);
            } catch (IllegalBlockSizeException e) {
                throw new BankingException("Key Creation Failure ", e);
            } catch (ClassNotFoundException e) {
                throw new BankingException("Casting Failure ", e);
            } catch (BadPaddingException e) {
                throw new BankingException("Key Creation Failure ", e);
            } finally {
                try {
                    SealedObject sealedBool = new SealedObject(success, cipher);
                    oos.writeObject((Object) sealedBool);
                    oos.flush();
                } catch (IOException e) {
                    throw new BankingException("IOException ", e);
                } catch (IllegalBlockSizeException e) {
                    throw new BankingException("Key Creation Failure ", e);
                }
            }
        } catch (InvalidKeyException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchAlgorithmException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchPaddingException e) {
            throw new BankingException("Key Creation Failure ", e);
        }
    }

    public void withdrawl(ObjectInputStream ois, ObjectOutputStream oos, Session session)
            throws BankingException {
        try {
            SymetricKeyGen symKey =
                    new SymetricKeyGen(session.getSecretKey(), session.getIvParameterSpec());

            Cipher cipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());

            Cipher decipher = Cipher.getInstance(SYMETRIC_ALGORITHM);
            decipher.init(Cipher.DECRYPT_MODE, symKey.getSecretKey(), symKey.getIVParams());
            boolean success = false;
            try {
                if (session.isAuthenticated()) {
                    long userId = -1L;

                    SealedObject sealedWithdrawl = (SealedObject) ois.readObject();
                    Transaction withdrawl = (Transaction) sealedWithdrawl.getObject(decipher);

                    PreparedStatement pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER);
                    pstmt.setString(1, session.getUserName());

                    ResultSet rs = pstmt.executeQuery();
                    if (rs.next()) {
                        userId = (long) rs.getInt(1);
                    }

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.GET_USER_ACCOUNTS);
                    pstmt.setInt(1, (int) userId);
                    rs = pstmt.executeQuery();

                    long withdrawlAcctId = -1L;
                    double withdrawlAccountVal = -1.0;
                    while (rs.next()) {
                        if ((long) rs.getInt(5) == withdrawl.getAccountNum()) {
                            withdrawlAcctId = (long) rs.getInt(1);
                            withdrawlAccountVal = rs.getDouble(7);
                            break;
                        }
                    }

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.INSERT_TRANSACTION);
                    pstmt.setInt(1, (int) withdrawl.getTransactionTypeId());
                    pstmt.setString(2, withdrawl.getTransactionDesc());
                    pstmt.setDouble(3, withdrawl.getTransactionValue());
                    pstmt.setInt(4, (int) withdrawlAcctId);

                    int result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    pstmt = db.getConnection().prepareStatement(DatabaseSingleton.UPDATE_ACCOUNT);
                    double newValue = ((withdrawl.getTransactionTypeId() % 2) == 1)
                            ? (withdrawlAccountVal + withdrawl.getTransactionValue())
                            : (withdrawlAccountVal - withdrawl.getTransactionValue());
                    pstmt.setDouble(1, newValue);
                    pstmt.setInt(2, (int) withdrawlAcctId);

                    result = pstmt.executeUpdate();
                    log.log(Level.FINE, "result= {0}", new Object[]{result});

                    success = true;
                }
            } catch (NullPointerException e) {
                throw new BankingException("Session was not initialized ", e);
            } catch (IOException e) {
                throw new BankingException("IOException ", e);
            } catch (SQLException e) {
                throw new BankingException("Failure in SQL Statement ", e);
            } catch (IllegalBlockSizeException e) {
                throw new BankingException("Key Creation Failure ", e);
            } catch (ClassNotFoundException e) {
                throw new BankingException("Casting Failure ", e);
            } catch (BadPaddingException e) {
                throw new BankingException("Key Creation Failure ", e);
            } finally {
                try {
                    SealedObject sealedBool = new SealedObject(success, cipher);
                    oos.writeObject((Object) sealedBool);
                    oos.flush();
                } catch (IOException e) {
                    throw new BankingException("IOException ", e);
                } catch (IllegalBlockSizeException e) {
                    throw new BankingException("Key Creation Failure ", e);
                }
            }
        } catch (InvalidKeyException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (InvalidAlgorithmParameterException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchAlgorithmException e) {
            throw new BankingException("Key Creation Failure ", e);
        } catch (NoSuchPaddingException e) {
            throw new BankingException("Key Creation Failure ", e);
        }
    }

    private byte[] hashPass(PasswordVerificationData usrData) {
        MessageDigest digest = null;
        byte[] retVal;

        try {
            digest = MessageDigest.getInstance(HASHING_FUNC);
        } catch (NoSuchAlgorithmException ex) {
            log.log(Level.SEVERE, null, ex);
        }

        digest.reset();
        digest.update(usrData.getNonce());
        retVal = digest.digest(usrData.getPassword());

        log.log(Level.INFO,
                "hash= {0}", new Object[]{Utils.toHexString(retVal)
                });

        return retVal;
    }
}
